package org.example.mall.admin.configuration.auth;

import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.example.mall.admin.configuration.auth.grant.CustomAuthenticationToken;
import org.example.mall.admin.configuration.auth.handler.AuthHandlerChain;
import org.example.mall.admin.constant.auth.AuthTypeConst;
import org.example.mall.admin.util.SecurityUtil;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.oauth2.provider.TokenRequest;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.stereotype.Component;

import javax.servlet.FilterChain;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.Map;

/**
 * 认证服务器 资源服务器 都注入
 *
 * @author hzq
 * @date 2021/12/6 11:15
 */
@Slf4j
@Component
@AllArgsConstructor
public class CustomUserDetailsService implements UserDetailsService {

    private final AuthHandlerChain authHandlerChain;

    /**
     * @see org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer#addUserDetailsService(DefaultTokenServices, UserDetailsService) 刷新token
     * @see DefaultTokenServices#refreshAccessToken(String, TokenRequest) 刷新token
     * @see org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationProvider#authenticate(Authentication) 刷新token
     * @see org.springframework.security.core.userdetails.UserDetailsByNameServiceWrapper#loadUserDetails(Authentication) 刷新token
     * <p>
     * 此项目设置 CustomLocalTokenServices 本地校验token 不使用 RemoteTokenServices 检验token不会调用 @link loadUserByUsername
     * </p>
     * @see org.springframework.security.oauth2.provider.authentication.OAuth2AuthenticationProcessingFilter#doFilter(ServletRequest, ServletResponse, FilterChain) 校验token
     * @see org.springframework.security.oauth2.provider.authentication.OAuth2AuthenticationManager#authenticate(Authentication) 校验token
     * @see DefaultTokenServices#loadAuthentication(String) 校验token
     * <p>
     * SSO
     * </p>
     * @see org.springframework.security.oauth2.client.filter.OAuth2ClientAuthenticationProcessingFilter#attemptAuthentication(HttpServletRequest, HttpServletResponse) 校验token
     * @see org.springframework.security.oauth2.provider.token.RemoteTokenServices#loadAuthentication(String) 校验token
     * @see org.springframework.security.oauth2.provider.token.DefaultUserAuthenticationConverter#extractAuthentication(Map) 校验token
     */
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        try {
            String[] split = SecurityUtil.splitUnionUsername(username);
            Map<String, String> details = new HashMap<>();
            details.put(AuthTypeConst.AUTH_TYPE, split[1]);
            return loadUser(AuthTypeConst.UNION_ID, split[0], null, details);
        } catch (IllegalArgumentException ignored) {
            return loadUser(AuthTypeConst.USERNAME, username, null, null);
        }
    }

    /**
     * 查询用户信息
     *
     * @param authentication 预认证token
     * @return 用户信息
     */
    public UserDetails loadUser(CustomAuthenticationToken authentication) {

        // 处理器类型
        String handlerType = authentication.getHandlerType();

        // 标识
        String principal = (String) authentication.getPrincipal();

        // 凭证
        String credentials = (String) authentication.getCredentials();

        // 参数
        Map<String, String> details = (Map<String, String>) authentication.getDetails();

        return loadUser(handlerType, principal, credentials, details);
    }

    /**
     * 处理认证信息
     *
     * @param handlerType 处理器类型
     * @param principal   标识
     * @param credentials 凭证
     * @param details     自定义参数
     */
    private UserDetails loadUser(String handlerType, String principal, String credentials, Map<String, String> details) {
        UserDetails userDetails = authHandlerChain.handler(handlerType, principal, credentials, details);
        if (userDetails == null) {
            throw new UsernameNotFoundException("用户不存在");
        }
        return userDetails;
    }
}
